/************************************************************************
 * fspy - experimental POC linux filesystem activity monitor            *
 * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ *
 * it's based on the new linux kernel inotify interface (merged into    *
 * the 2.6.13 linux kernel)                                             *
 * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ *
 * this code was tested on 2.6.18.4 and newer kernels                   *
 ************************************************************************
 * Copyright (C) 2007  Richard Sammet (e-axe)                           *
 *                                                                      *
 * This program is free software; you can redistribute it and/or modify *
 * it under the terms of the GNU General Public License as published by *
 * the Free Software Foundation; either version 2 of the License, or    *
 * (at your option) any later version.                                  *
 *                                                                      *
 * This program is distributed in the hope that it will be useful,      *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of       *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
 * GNU General Public License for more details.                         *
 *                                                                      *
 * http://www.gnu.org/licenses/gpl.txt                                  *
 ************************************************************************
 * Contact, Bugs & Infos:                                               *
 *     richard.sammet@gmail.com                                         *
 ************************************************************************
 * Some infos:                                                          *
 *  - tabstop size:                                                     *
 *    set ts=2                                                          *
 ************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/inotify.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include "fspy.h"
#include "output.h"
#include "fsevents.h"
#include "stating.h"
#include "diff.h"

int log_mode = 0;
char *logfile = NULL;
int output_fd = -1;
pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;

/* rotate log file. Should be called by a signal handler (on SIGHUP)
 * only do something when logmode is set to LOGFILE. Useless otherwise
 * for the time being, only rotate on one file
 */
void rotate_log() {
	char *rotate_name;
	char *previous_name;
	if(log_mode == LOGFILE) {
		//lock
		pthread_mutex_lock(&log_mutex);
		rotate_name = (char*)malloc(strlen(logfile)+strlen(".0")+1);
		if(rotate_name!=NULL) {
			close(output_fd);		
			previous_name=logfile;
			strcpy(rotate_name, logfile);
			strcat(rotate_name, ".0");
			rename(logfile,rotate_name);
			init_log(LOGFILE, logfile);
			// do not forget to free this pointer. Remember that init_log use strdup(arg)
			free(previous_name);
		}
		pthread_mutex_unlock(&log_mutex);
		//unlock
	}
}

/* init log management. 3 possibilities (exclusive)
 * stdout : do nothing
 * syslog : open syslog facility and log into it
 * logfile: close stdout (not stderr), redirect everything into a file
 */
int init_log(int mode, char *arg) {
	switch(mode) {
		case SYSLOG:
			log_mode = SYSLOG;
			openlog("fspy", LOG_ODELAY, LOG_AUTH);
			fprintf(stderr, "Output: using syslog\n");
			break;
		case STDOUT:
			log_mode = STDOUT;
			fprintf(stderr, "Output: using stdout\n");
			break;
		case LOGFILE:
			log_mode = LOGFILE;
			if(arg!=NULL) {
				logfile = strdup(arg);
				output_fd = open(logfile, O_CREAT|O_APPEND|O_WRONLY,S_IRUSR|S_IWUSR);
				if(output_fd == -1 ) {
					return -1;
				}
				fprintf(stderr, "Output: using logfile <%s>\n", arg);
			    dup2(output_fd, fileno(stdout));
			    //dup2(output_fd, fileno(stderr));
			} else {
				return -1;
			}

			break;
		default:
			return -1;
			break;
	}
	return 0;
}

void write_log(char *msg) {
	switch(log_mode) {
		case SYSLOG:
			syslog(LOG_INFO, msg, strlen(msg));
			break;
		case STDOUT:
			fprintf(stdout, "%s", msg);
			break;
		case LOGFILE:
			// lock
			pthread_mutex_lock(&log_mutex);
			write(output_fd, msg, strlen(msg));
			pthread_mutex_unlock(&log_mutex);
			//unlock
			break;
	}
}


void print_data(char *mystrin, struct inotify_event *event, const char *event_fpath, struct stat *statdat, struct diffprint *dprint) {

  char *mystrin_tmp;
  char *mystr;
  char mychar;
  char desc_ptr[128] = {0};
  char *ctp;
  char fpath[ELEMENT_SIZE * 2] = {0};
  char *freeme;

  char buffer[BUF_LEN];
  int  index=0;

  const char *const normal = "\033[0m";
  const char *const marked = "\033[1;31m";

  time_t currtime;
  
  memset(buffer, 0, BUF_LEN);
  snprintf(fpath, (ELEMENT_SIZE * 2), "%s%s", event_fpath, event->name);

  mystrin_tmp = strdup(mystrin);

  while((mystr = strtok(mystrin_tmp, DELIM))) {
    if(strlen(mystr) == 1) {
      mychar = mystr[0];

      switch(mychar) {
        case  'f':  add2buf(buffer,index, "%s", event->name);
                    break;
        case  'p':  add2buf(buffer,index,"%s", event_fpath);
                    break;
        case  'd':  memset(desc_ptr, 0, 128 * sizeof(char)); 
                    add2buf(buffer,index,"%s", get_event_desc(event->mask, desc_ptr));
                    break;
        case  'w':  add2buf(buffer,index,"%i", event->wd);
                    break;
        case  'c':  add2buf(buffer,index,"%u", event->cookie);
                    break;
        case  'm':  add2buf(buffer,index,"0x%08x", event->mask);
                    break;
        case  'l':  add2buf(buffer,index,"%u", event->len);
                    break;
        case  't':  memset(desc_ptr, 0, 128 * sizeof(char)); 
                    add2buf(buffer,index,"%s", gettype(fpath, desc_ptr, statdat));
                    break;
                    /* yeah, i know, thats some kind of dirty style ;)
                       but i dont like the additional newline at EOL... */
        case  'A':  ctp = ctime(&statdat->st_atime); ctp[strlen(ctp) - 1] = '\0';
                    (dprint->A == 1)?add2buf(buffer,index,"%s%s%s", marked, ctp, normal):add2buf(buffer,index,"%s", ctp);
                    break;
        case  'M':  ctp = ctime(&statdat->st_mtime); ctp[strlen(ctp) - 1] = '\0';
                    (dprint->M == 1)?add2buf(buffer,index,"%s%s%s", marked, ctp, normal):add2buf(buffer,index,"%s", ctp);
                    break;
        case  'S':  ctp = ctime(&statdat->st_ctime); ctp[strlen(ctp) - 1] = '\0';
                    (dprint->S == 1)?add2buf(buffer,index,"%s%s%s", marked, ctp, normal):add2buf(buffer,index,"%s", ctp);
                    break;
        case  'T':  currtime = time(NULL);
                    ctp = ctime(&currtime); ctp[strlen(ctp) - 1] = '\0';
                    add2buf(buffer,index,"%s", ctp);
                    break;
        case  's':  (dprint->s == 1)?add2buf(buffer,index,"%s%lld%s", marked, (long long int) statdat->st_size, normal):add2buf(buffer,index,"%lld", (long long int) statdat->st_size);
                    break;
        case  'U':  (dprint->U == 1)?add2buf(buffer,index,"%s%ld%s", marked, (long int) statdat->st_uid, normal):add2buf(buffer,index,"%ld", (long int) statdat->st_uid);
                    break;
        case  'G':  (dprint->G == 1)?add2buf(buffer,index,"%s%ld%s", marked, (long int) statdat->st_gid, normal):add2buf(buffer,index,"%ld", (long int) statdat->st_gid);
                    break;
        case  'O':  (dprint->O == 1)?add2buf(buffer,index,"%s%lo%s", marked, (unsigned long int) statdat->st_mode, normal):add2buf(buffer,index,"%lo", (unsigned long int) statdat->st_mode);
                    break;
        case  'I':  (dprint->I == 1)?add2buf(buffer,index,"%s%ld%s", marked, (long int) statdat->st_ino, normal):add2buf(buffer,index,"%ld", (long int) statdat->st_ino);
                    break;
        case  'D':  (dprint->D == 1)?add2buf(buffer,index,"%s%ld%s", marked, (long int) statdat->st_dev, normal):add2buf(buffer,index,"%ld", (long int) statdat->st_dev);
                    break;
        default  :  add2buf(buffer,index,"%c", mychar);
                    break;
      }
    }else{
      add2buf(buffer,index,"%s", mystr);
    }

    freeme = mystrin_tmp;
    mystrin_tmp = NULL;
  }

  add2buf(buffer,index,"\n");

  write_log(buffer);

  free(freeme);
}
